/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.taglibs.secure;

import com.codemagi.util.Utils;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import org.apache.log4j.Logger;

/**
 * This tag library is designed to be used on a JSP to switch HTTP -> HTTPS
 * protocols and vise versa.
 * 
 * If you want to force the page to be viewed in SSL, then you would do
 * something like this: <br />
 * <br />
 * 
 * <pre>
 *  &lt;tag:secure /&gt;
 *  or
 *  &lt;tag:secure mode=&quot;secured&quot; /&gt;
 * </pre>
 * 
 * If you want the force the page to be viewed in over standard http, then you
 * would do something like: <br />
 * 
 * <pre>
 *  &lt;tag:secure mode=&quot;unsecured&quot; /&gt;
 * </pre>
 * 
 * @jsp.tag name="secure" bodycontent="empty"
 * @author Jon Lipsky (Original author)
 * 
 * Contributed by:
 * XEsoft GmbH Oskar-Messter-Strasse 18 85737 Ismaning, Germany
 * http://www.xesoft.com
 *
 * @author August Detlefsen for CodeMagi, Inc. (Extensions to work with SSL accelerators)
 */
public class SecureTag extends BodyTagSupport {

    Logger log = Logger.getLogger(this.getClass());

    //~ Static fields/initializers
    // =============================================
    public static final String MODE_SECURED   = "secured";
    public static final String MODE_UNSECURED = "unsecured";
    public static final String MODE_EITHER    = "either";

    public static final String STD_HTTP_PORT  = "80";
    public static final String STD_HTTPS_PORT = "443";

    //~ Instance fields
    // ========================================================
    protected String TAG_NAME = "Secure";

    private String mode             = MODE_SECURED;
    private String forwardPage      = "";    //optionally override forward page 
    private String httpPort         = null;
    private String httpsPort        = null;
    private String httpsHeaderName  = null;
    private String httpsHeaderValue = null;


    /**
     * Sets the mode attribute. This is included in the tld file.
     * 
     * @jsp.attribute description="The mode attribute (secure | unsecured)"
     *                required="false" rtexprvalue="true"
     */
    public void setMode(String aMode) {
        mode = aMode;
    }

    public void setForwardPage(String newValue) {
	if (!Utils.isEmpty(newValue)) forwardPage = newValue;
    }

    public int doStartTag() throws JspException {

        // get the port numbers
        ServletContext ctx = pageContext.getServletContext();
        httpPort = (String)ctx.getInitParameter("securelogin.http.port");

        if (httpPort == null) {
	    httpPort = STD_HTTP_PORT;
	}

        httpsPort = (String)ctx.getInitParameter("securelogin.https.port");
	if (httpsPort == null) {
	    httpsPort = STD_HTTPS_PORT;
	}
        httpsHeaderName  = (String)ctx.getInitParameter("securelogin.https.headername");
        httpsHeaderValue = (String)ctx.getInitParameter("securelogin.https.headervalue");

	log.debug("headerName: " + httpsHeaderName + " headerValue: " + httpsHeaderValue);
        return SKIP_BODY;
    }


    public int doAfterBody() throws JspException {
        return SKIP_BODY;
    }


    public int doEndTag() throws JspException {

        HttpServletRequest req = (HttpServletRequest)pageContext.getRequest();
        if (mode.equalsIgnoreCase(MODE_SECURED)) {
	    if (!isSecure((HttpServletRequest)pageContext.getRequest())) {
		String vQueryString = req.getQueryString();
		String vServer      = req.getServerName();

		String vPageUrl     = forwardPage; 
		if (Utils.isEmpty(vPageUrl)) vPageUrl = (String)req.getAttribute("javax.servlet.forward.request_uri");
		if (Utils.isEmpty(vPageUrl)) vPageUrl = req.getRequestURI();

		StringBuffer vRedirect = new StringBuffer("");
		vRedirect.append("https://");
		if (httpsPort == null || httpsPort.trim().length() == 0 || httpsPort.equals(STD_HTTPS_PORT)) {
		    vRedirect.append(vServer + vPageUrl);

		} else {
		    vRedirect.append(vServer + ":" + httpsPort + vPageUrl);

		}
		
		if (vQueryString != null) {
		    vRedirect.append("?");
		    vRedirect.append(vQueryString);
		}

		log.debug("attempting to redirect to: " + vRedirect);

                try {
		    ((HttpServletResponse) pageContext.getResponse()).sendRedirect(vRedirect.toString());
		    return SKIP_PAGE;

		} catch (Exception exc2) {
		    log.error("", exc2);

		    throw new JspException(exc2);
		}
	    }

	} else if (mode.equalsIgnoreCase(MODE_UNSECURED)) {

	    if (isSecure((HttpServletRequest)pageContext.getRequest())) {
		String vQueryString = req.getQueryString();
		String vServer      = req.getServerName();

		String vPageUrl     = forwardPage;
		if (Utils.isEmpty(vPageUrl)) vPageUrl = (String)req.getAttribute("javax.servlet.forward.request_uri");
		if (Utils.isEmpty(vPageUrl)) vPageUrl = req.getRequestURI();

		StringBuffer vRedirect = new StringBuffer("");
		vRedirect.append("http://");
		if (!httpPort.equals(STD_HTTP_PORT)) {
		    vRedirect.append(vServer + ":" + httpPort + vPageUrl);

		} else {
		    vRedirect.append(vServer + vPageUrl);
		}
		
		if (vQueryString != null) {
		    vRedirect.append("?");
		    vRedirect.append(vQueryString);
		}

                try {
		    ((HttpServletResponse) pageContext.getResponse()).sendRedirect(vRedirect.toString());
		    return SKIP_PAGE;

		} catch (Exception exc2) {
		    throw new JspException(exc2.getMessage());
		}
	    }

	} else if (mode.equalsIgnoreCase(MODE_EITHER)) {
	    return EVAL_PAGE;

	} else {
	    throw new JspException("Illegal value for the attribute mode: " + mode);
	}

        return EVAL_PAGE;
    }
    
    /**
     * Returns true if this request is secure, either by the <code>request.isSecure()</code> method or 
     * by checking for the presence of a request header.
     */
    private boolean isSecure(HttpServletRequest request) {
	
	log.debug("SecureTag.isSecure() called...");

	//first try the java internal method
	if (request.isSecure()) {
	    log.debug("Connection secure according to ServletRequest");
	    return true;
	}

	//if there is an ssl accelerator in use, check for the existence of 
	//headers indicating that the accelerator has processed the request
        boolean secure = false;
        if ( !Utils.isEmpty(httpsHeaderName) ) {

            String headerValue = request.getHeader(httpsHeaderName);
	    log.debug("headerValue      = " + headerValue);
	    log.debug("httpsHeaderValue = " + httpsHeaderValue);

	    if (!Utils.isEmpty(headerValue)) {
		log.debug("EQUAL: " + headerValue.contains(httpsHeaderValue));
		secure = headerValue.contains(httpsHeaderValue); 
	    }
        }

        log.debug("Connection secure = " + secure);
        return secure;
    }

    /*
    private boolean isSecure(HttpServletRequest request) {
        boolean secure = false;
        if (httpsHeaderName == null) {
            secure = request.isSecure();
	    log.debug("httpsHeaderName == null SECURE: " + request.isSecure());

        } else {
            String headerValue = request.getHeader(httpsHeaderName);
	    log.debug("headerValue =      " + headerValue);
	    log.debug("httpsHeaderValue = " + httpsHeaderValue);
	    if (!Utils.isEmpty(headerValue)) {
		log.debug("EQUAL: " + headerValue.contains(httpsHeaderValue));
		secure = headerValue.contains(httpsHeaderValue); //Utils.isEmpty(httpsHeaderValue) || httpsHeaderValue.equals(headerValue);
	    }
        }

        log.debug("Connection secure = " + secure);
        return secure;
    }
    */

}
